package com.txc.cloud.auth.common.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.txc.cloud.auth.model.dto.User;
import com.txc.cloud.auth.model.vo.CurrentUser;
import com.txc.common.global.result.Result;
import com.txc.common.global.result.ResultCode;
import com.txc.common.utils.format.ObjectFormat;
import com.txc.common.utils.jwt.RSAUtils;
import com.txc.common.utils.jwt.TokenManager;
import com.txc.common.utils.middleware.RedisUtils;
import com.txc.common.utils.supply.HttpServletResponseUtil;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;

/**
 * 登陆拦截器
 *
 * @ClassName: AuthLoginFilter
 * @Author: TXC
 * @Date: 2023-07-30 21:10
 **/

public class AuthLoginFilter extends UsernamePasswordAuthenticationFilter {
    private final TokenManager tokenManager;
    private final RedisUtils redisUtils;
    private final AuthenticationManager authenticationManager;

    public AuthLoginFilter(TokenManager tokenManager, RedisUtils redisUtils, AuthenticationManager authenticationManager) {
        this.tokenManager = tokenManager;
        this.redisUtils = redisUtils;
        this.authenticationManager = authenticationManager;
        // 设置登陆接口请求方式为GET或POST
        this.setPostOnly(false);
        // 设置登陆接口
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login", "POST"));
    }


    /**
     * 认证执行的方法
     *
     * @param request  from which to extract parameters and perform the authentication
     * @param response the response, which may be needed if the implementation has to do a
     *                 redirect as part of a multi-stage authentication process (such as OpenID).
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        // 获取表单提供的数据
        ObjectMapper mapper = new ObjectMapper();
        try {
            User user = mapper.readValue(request.getInputStream(), User.class);
            return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(),
                    RSAUtils.decrypt(user.getPassword()), new ArrayList<>()/*权限*/));
        } catch (Exception e) {
            throw new BadCredentialsException(ResultCode.USER_CREDENTIALS_ERROR.getMessage());
        }
    }

    /**
     * 认证成功执行方法
     *
     * @param request    from which to extract parameters and perform the authentication
     * @param response   the response, which may be needed if the implementation has to do a
     *                   redirect as part of a multi-stage authentication process (such as OpenID).
     * @param chain      the <tt>FilterChain</tt> that this filter is part of.
     * @param authResult the object returned from the <tt>attemptAuthentication</tt>
     *                   method.
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                            FilterChain chain, Authentication authResult) throws IOException,
            ServletException {
        CurrentUser user = (CurrentUser) authResult.getPrincipal();
        String userId = user.getUserDetail().getId().toString();
        String token = tokenManager.createToken(user.getUsername(), user.getUserDetail().getId().toString());
        redisUtils.set(tokenManager.getCacheKey() + userId, ObjectFormat.objectToStr(user.getUserDetail()), tokenManager.getExpire(), TimeUnit.MILLISECONDS);
        HttpServletResponseUtil.out(response, Result.ok(token));
    }

    /**
     * 认证失败执行的方法
     *
     * @param request  from which to extract parameters and perform the authentication
     * @param response the response, which may be needed if the implementation has to do a
     *                 redirect as part of a multi-stage authentication process (such as OpenID).
     * @param failed   the exception thrown during authentication.
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                              AuthenticationException failed) throws IOException, ServletException {
        if (failed instanceof BadCredentialsException) {
            HttpServletResponseUtil.out(response, Result.fail(ResultCode.USER_CREDENTIALS_ERROR));
        }
        if (failed instanceof UsernameNotFoundException) {
            HttpServletResponseUtil.out(response, Result.fail(ResultCode.USER_ACCOUNT_NOT_EXIST));
        }
        HttpServletResponseUtil.out(response, Result.fail(HttpStatus.UNAUTHORIZED.value(), failed.getMessage()));
    }
}
